﻿using System;
using System.Collections.Generic;
using System.IO;

namespace ooWii.IO
{
	public class SplitFileStream : Stream
	{
		#region SplitFileStream

		Stream[] streams;
		long position;

		/// <summary>
		/// If we are trying to replace any of these characters with the file index then the result will be invalid.
		/// </summary>
		readonly char[] invalidChars = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '/', '\\', ':' };

		public SplitFileStream( string name )
		{
			List<Stream> streamList = new List<Stream>();

			try
			{
				streamList.Add( File.OpenRead( name ) );

				int i = 1;
				while( true )
				{
					string newEnd = i.ToString();
					string oldEnd = name.Substring( name.Length - newEnd.Length );

					if( oldEnd.IndexOfAny( invalidChars ) >= 0 )
					{
						throw new ArgumentException( "Invalid characters in suffix", "name" );
					}

					string partName = name.Substring( 0, name.Length - newEnd.Length ) + newEnd;

					if( !File.Exists( partName ) )
					{
						break;
					}

					streamList.Add( File.OpenRead( partName ) );
					i++;
				}
			}
			catch
			{
				for( int i = 0; i < streamList.Count; i++ )
				{
					streamList[ i ].Close();
				}

				throw;
			}

			streams = streamList.ToArray();
		}

		#endregion

		#region Stream

		public override void Close()
		{
			for( int i = 0; i < streams.Length; i++ )
			{
				streams[ i ].Close();
			}

			base.Close();
		}

		public override bool CanRead
		{
			get
			{
				return true;
			}
		}

		public override bool CanSeek
		{
			get
			{
				return true;
			}
		}

		public override bool CanWrite
		{
			get
			{
				return false;
			}
		}

		public override void Flush()
		{
		}

		public override long Length
		{
			get
			{
				long length = 0;

				for( int i = 0; i < streams.Length; i++ )
				{
					length += streams[ i ].Length;
				}

				return length;
			}
		}

		public override long Position
		{
			get
			{
				return position;
			}
			set
			{
				if( value < 0 )
				{
					throw new ArgumentOutOfRangeException( "value", "Non-negative number required." );
				}

				position = value;
			}
		}

		public override int Read( byte[] buffer, int offset, int count )
		{
			if( buffer == null )
			{
				throw new ArgumentNullException( "buffer" );
			}

			if( buffer.Length < offset + count )
			{
				throw new ArgumentException();
			}

			int part = 0;
			long partPosition = position;

			while( part < streams.Length - 1 && partPosition > streams[ part ].Length )
			{
				partPosition -= streams[ part ].Length;
				part++;
			}

			int totalRead = 0;
			while( totalRead < count && part < streams.Length )
			{
				int bytes = count - totalRead;
				long bytesLeft = streams[ part ].Length - partPosition;
				if( bytes > bytesLeft )
				{
					bytes = (int)bytesLeft;
				}

				streams[ part ].Position = partPosition;

				if( streams[ part ].Read( buffer, offset + totalRead, bytes ) != bytes )
				{
					throw new WiiException( "Cannot read split stream" );
				}

				part++;
				partPosition = 0;
				totalRead += bytes;
			}

			position += totalRead;

			return totalRead;
		}

		public override long Seek( long offset, SeekOrigin origin )
		{
			long newPosition;

			switch( origin )
			{
			case SeekOrigin.Begin:
				newPosition = offset;
				break;

			case SeekOrigin.Current:
				newPosition = this.Position + offset;
				break;

			case SeekOrigin.End:
				newPosition = this.Length + offset;
				break;

			default:
				throw new ArgumentException( "origin" );
			}

			if( newPosition < 0 )
			{
				throw new IOException( "An attempt was made to move the file pointer before the beginning of the file." );
			}

			position = newPosition;
			return position;
		}

		public override void SetLength( long value )
		{
			throw new NotSupportedException();
		}

		public override void Write( byte[] buffer, int offset, int count )
		{
			throw new NotSupportedException();
		}

		#endregion
	}
}
